#pragma once
// 和网络  中 客户端请求数据有关的代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>   //uintptr_t
#include <stdarg.h>   //va_start....
#include <unistd.h>   //STDERR_FILENO等
#include <sys/time.h> //gettimeofday
#include <time.h>     //localtime_r
#include <fcntl.h>    //open
#include <errno.h>    //errno
// #include <sys/socket.h>
#include <sys/ioctl.h> //ioctl
#include <arpa/inet.h>

#include "ngx_c_conf.h"
#include "ngx_macro.h"
#include "ngx_global.h"
#include "ngx_func.h"
#include "ngx_c_socket.h"
#include "ngx_c_memory.h"

// 来数据时候的处理，当连接上有数据来的时候，本函数会被ngx_epoll_process_events()所调用  ,官方的类似函数为ngx_http_wait_request_handler();
void CSocekt::ngx_wait_request_handler(lpngx_connection_t c)
{
    ssize_t reco = recvproc(c, c->precvbuf, c->irecvlen);
    if (reco <= 0)
    {
        return; // 什么都不做
    }

    if (c->curStat == _PKG_HD_INIT)
    {
        if (reco == m_iLenPkgHeader) // 收到完整包头
        {
            ngx_wait_request_handler_proc_p1(c); // 那就调用专门针对包头处理完整的函数去处理把。
        }
        else // 缺包
        {
            c->curStat = _PKG_HD_RECVING;     // 接收包头中，包头不完整，继续接收包头中
            c->precvbuf = c->precvbuf + reco; // 注意收后续包的内存往后走
            c->irecvlen = c->irecvlen - reco; // 要收的内容当然要减少，以确保只收到完整的包头先
        }
    }
    else if (c->curStat == _PKG_HD_RECVING)
    {
        if (reco == c->irecvlen)
        {
            // 包头收完整了
            ngx_wait_request_handler_proc_p1(c); // 那就调用专门针对包头处理完整的函数去处理把。
        }
        else
        {
            // 包头还是没收完整，继续收包头
            c->precvbuf = c->precvbuf + reco; // 注意收后续包的内存往后走
            c->irecvlen = c->irecvlen - reco; // 要收的内容当然要减少，以确保只收到完整的包头先
        }
    }
    else if (c->curStat == _PKG_BD_INIT)
    {
        if (reco == c->irecvlen)
        {
            ngx_wait_request_handler_proc_plast(c);
        }
        else
        {
            // 收到的宽度小于要收的宽度
            c->curStat = _PKG_BD_RECVING;
            c->precvbuf = c->precvbuf + reco;
            c->irecvlen = c->irecvlen - reco;
        }
    }
    else if (c->curStat == _PKG_BD_RECVING)
    {
        if (c->irecvlen == reco)
        {
            // 包体收完整了
            ngx_wait_request_handler_proc_plast(c);
        }
        else
        {
            // 包体没收完整，继续收
            c->precvbuf = c->precvbuf + reco;
            c->irecvlen = c->irecvlen - reco;
        }
    }
    return;
}

// recv 封装
// 参数c：连接池中相关连接
// 参数buff：接收数据的缓冲区
// 参数buflen：要接收的数据大小
// 返回值：返回-1，则是有问题发生并且在这里把问题处理完毕了，调用本函数的调用者一般是可以直接return
//         返回>0，则是表示实际收到的字节数
ssize_t CSocekt::recvproc(lpngx_connection_t c, char *buff, ssize_t buflen)
{
    ssize_t n;
    n = ::recv(c->fd, buff, buflen, 0);
    if (n == 0)
    {
        // 客户端关闭【应该是正常完成了4次挥手】，我这边就直接回收连接连接，关闭socket即可
        //    ngx_log_stderr(0,"连接被客户端正常关闭[4路挥手关闭]！");
        ngx_close_connection(c);
        return -1;
    }
    if (n < 0)
    {
        if (errno == EAGAIN || errno == EWOULDBLOCK)
        {
            // 我认为LT模式不该出现这个errno，而且这个其实也不是错误，所以不当做错误处理
            ngx_log_stderr(errno, "CSocekt::recvproc()中errno == EAGAIN || errno == EWOULDBLOCK成立，出乎我意料！"); // epoll为LT模式不应该出现这个返回值，所以直接打印出来瞧瞧
            return -1;                                                                                               // 不当做错误处理，只是简单返回
        }

        if (errno == EINTR)
        {
            ngx_log_stderr(errno, "CSocekt::recvproc()中errno == EINTR成立，出乎我意料！");
            return -1;
        }

        ngx_log_stderr(errno, "CSocekt::recvproc()中发生错误，我打印出来看看是啥错误！");
        // 这种真正的错误就要，直接关闭套接字，释放连接池中连接了
        ngx_close_connection(c);
        return -1;
    }
    return n; // 返回收到的字节数
}

// 接收完包头后
void CSocekt::ngx_wait_request_handler_proc_p1(lpngx_connection_t c)
{
    CMemory *p_memory = CMemory::GetInstance();

    LPCOMM_PKG_HEADER pPkgHeader;
    pPkgHeader = (LPCOMM_PKG_HEADER)c->dataHeadInfo; // 正好收到包头时，包头信息肯定是在dataHeadInfo里；

    unsigned short e_pkgLen;
    e_pkgLen = ntohs(pPkgHeader->pkgLen);

    // 判断包头是否合法
    if (pPkgHeader->pkgLen < m_iLenPkgHeader) // 总长度比包头还小 伪造包或错误包
    {
        // 重新初始化
        c->curStat = _PKG_HD_INIT;
        c->precvbuf = c->dataHeadInfo;
        c->irecvlen = m_iLenPkgHeader;
    }
    else if (e_pkgLen > (_PKG_MAX_LENGTH - 1000)) // 包大于限定长度，不合法
    {
        // 重新初始化
        c->curStat = _PKG_HD_INIT;
        c->precvbuf = c->dataHeadInfo;
        c->irecvlen = m_iLenPkgHeader;
    }
    else // 合法包
    {
        // 开辟一块内存来存包体 大小，消息头部加上包大小
        char *pTmpBuffer = (char *)p_memory->AllocMemory(e_pkgLen + m_iLenMsgHeader, false);
        c->ifnewrecvMem = true;         // 标记我们new了内存，将来在ngx_free_connection()要回收的
        c->pnewMemPointer = pTmpBuffer; // 内存开始指针

        // 1.先填消息头部
        LPSTRUC_MSG_HEADER ptmpMsgHeader = (LPSTRUC_MSG_HEADER)pTmpBuffer;
        ptmpMsgHeader->iCurrsequence = c->iCurrsequence;
        ptmpMsgHeader->pConn = c;
        // 2.再填写包头内容
        pTmpBuffer += m_iLenMsgHeader; // 指针跳过消息头
        memcpy(pTmpBuffer, pPkgHeader, m_iLenPkgHeader);
        if (e_pkgLen == m_iLenPkgHeader)
        {
            // 该报文只有包头无包体【我们允许一个包只有包头，没有包体】
            // 这相当于收完整了，则直接入消息队列待后续业务逻辑线程去处理吧
            ngx_wait_request_handler_proc_plast(c);
        }
        else
        {
            // 为接收包体做准备
            c->curStat = _PKG_BD_INIT;                  // 当前状态发生改变，包头刚好收完，准备接收包体
            c->precvbuf = pTmpBuffer + m_iLenPkgHeader; // pTmpBuffer指向包头，这里 + m_iLenPkgHeader后指向包体位置
            c->irecvlen = e_pkgLen - m_iLenPkgHeader;   // e_pkgLen是整个包【包头+包体】大小，-m_iLenPkgHeader【包头】  = 包体
        }
    }
    return;
}

// 收到一个完整包后的处理【plast表示最后阶段】，放到一个函数中，方便调用
void CSocekt::ngx_wait_request_handler_proc_plast(lpngx_connection_t c)
{
    // 把这段内存放到消息队列中来；
    inMsgRecvQueue(c->pnewMemPointer);
    //......这里可能考虑触发业务逻辑，怎么触发业务逻辑，这个代码以后再考虑扩充。。。。。。

    // 初始化
    c->ifnewrecvMem = false; // 内存不再需要释放，因为你收完整了包，这个包被上边调用inMsgRecvQueue()移入消息队列，那么释放内存就属于业务逻辑去干，不需要回收连接到连接池中干了
    c->pnewMemPointer = NULL;
    c->curStat = _PKG_HD_INIT;     // 收包状态机的状态恢复为原始态，为收下一个包做准备
    c->precvbuf = c->dataHeadInfo; // 设置好收包的位置
    c->irecvlen = m_iLenPkgHeader; // 设置好要接收数据的大小
    return;
}

//---------------------------------------------------------------
// 当收到一个完整包之后，将完整包入消息队列，这个包在服务器端应该是 消息头+包头+包体 格式
void CSocekt::inMsgRecvQueue(char *buf) // buf这段内存 ： 消息头 + 包头 + 包体
{
    m_MsgRecvQueue.push_back(buf);

    // 为了测试方便，因为本函数意味着收到了一个完整的数据包，所以这里打印一个信息
    ngx_log_stderr(0, "非常好，收到了一个完整的数据包（包头+包体）！");
}
